////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNTypeManagerImpl.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNNodeHelpers.h"
#include "WNLogging/inc/WNLog.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/IRBuilder.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

using namespace WNScripting;

WNTypeManagerImpl::WNTypeManagerImpl() {
    mNextTag[0] = 'A';
    mNextTag[1] = 'A';
    mNextTag[2] = 'A';
    mNextTag[3] = 'A';
}

WNTypeManagerImpl::~WNTypeManagerImpl() {

}

void WNTypeManagerImpl::IncrementTag() {
    for(size_t i = 0; i < 4; ++i) {
        if(mNextTag[i] < 'z') {
            mNextTag[i] += 1;
            break;
        }
        else
        {
            mNextTag[i] = 'A';
        }
    }
}


WN_VOID GenerateAssertion(WNCodeModule& _module) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    WNFunctionDefinition* func = _module.GetFunctionDefinition("Assert", std::vector<WNScriptType>());
    if(!func) {
        return;
    }
    builder->CreateCall(func->mFunction);
}

eWNTypeError WNTypeManagerImpl::RegisterScalarType(const WN_CHAR* name, WN_FLOAT32 priority, WNScriptType& _outType, llvm::Type* _type) {
    if(GetTypeByName(name, _outType) == eWNOK) {
        return(eWNAlreadyExists);
    }
    WNScripting::WNScriptTypeInternal* type = WN_NEW WNScripting::WNScriptTypeInternal(name, 0, priority, _type, mNextTag);
    IncrementTag();
    mScriptTypes.push_back(type);
    _outType = type;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterStructType(const WN_CHAR* name, WNScriptType& _outType) {
    if(GetTypeByName(name, _outType) == eWNOK) {
        return(eWNAlreadyExists);
    }
    llvm::StructType* sType = llvm::StructType::create(llvm::getGlobalContext());
    sType->setName(name);
    llvm::PointerType* ty = sType->getPointerTo(0);
#ifdef _WN_64_BIT
    llvm::Type* structCount = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* structCount = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif
    llvm::Type* types[3] = { llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext()), ty, structCount };
    llvm::StructType* sContainerType = llvm::StructType::create(llvm::getGlobalContext(), llvm::makeArrayRef(types));

    ty = sContainerType->getPointerTo(0);
    WNScripting::WNScriptTypeInternal* type = WN_NEW WNScripting::WNScriptTypeInternal(name, 0, 0, ty, sType, mNextTag);
    IncrementTag();
    mScriptTypes.push_back(type);
    _outType = type;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterArithmeticOperator(WNArithmeticType _type, WNScriptType _operand1, WNScriptType _operand2,  GenerateArithmeticOperation* _operation) {
    mArithmeticStructs.push_back(GenerateArithmeticStruct());
    mArithmeticStructs.back().mArithmeticType = _type;
    mArithmeticStructs.back().mType1 = _operand1;
    mArithmeticStructs.back().mType2 = _operand2;
    mArithmeticStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterCastingOperator(WNScriptType _fromType, WNScriptType _toType, GenerateCastingOperation* _operation) {
    mCastingStructs.push_back(GenerateCastingStruct());
    mCastingStructs.back().mType1 = _fromType;
    mCastingStructs.back().mType2 = _toType;
    mCastingStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterPreUnaryOperator(WNUnaryType _type, WNScriptType _operand, GeneratePreUnaryOperation* _operation) {
    mPreUnaryStructs.push_back(GeneratePreUnaryStruct());
    mPreUnaryStructs.back().mUnaryType = _type;
    mPreUnaryStructs.back().mType = _operand;
    mPreUnaryStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterPostUnaryOperator(WNPostUNType _type, WNScriptType _operand, GeneratePostUnaryOperation* _operation) {
    mPostUnaryStructs.push_back(GeneratePostUnaryStruct());
    mPostUnaryStructs.back().mPostUnaryType = _type;
    mPostUnaryStructs.back().mType = _operand;
    mPostUnaryStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterArrayAccessOperator(WNScriptType _operand1, WNScriptType _operand2, GenerateArrayAccessOperation* _operation) {
    mArrayAccessStructs.push_back(GenerateArrayAccessStruct());
    mArrayAccessStructs.back().mType1 = _operand1;
    mArrayAccessStructs.back().mType2 = _operand2;
    mArrayAccessStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterIDAccessOperator(WNScriptType _operand1, GenerateIDAccessOperation* _operation) {
    mIDAccessStructs.push_back(GenerateIDAccessStruct());
    mIDAccessStructs.back().mType1 = _operand1;
    mIDAccessStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterConstantOperator(WNScriptType _type, GenerateConstantOperation* _operation) {
    mConstantStructs.push_back(GenerateConstantStruct());
    mConstantStructs.back().mType = _type;
    mConstantStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterAllocationOperator(WNScriptType _type, GenerateAllocation* _operation) {
    mAllocationStructs.push_back(GenerateAllocationStruct());
    mAllocationStructs.back().mType = _type;
    mAllocationStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterAssignmentOperator(WNScriptType _type, WNAssignType _assignType, GenerateAssignment* _operation) {
    mAssignmentStructs.push_back(GenerateAssignmentStruct());
    mAssignmentStructs.back().mType = _type;
    mAssignmentStructs.back().mOperation = _operation;
    mAssignmentStructs.back().mAssignType = _assignType;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::RegisterDestructionOperator(WNScriptType _type, GenerateDestruction* _operation) {
    mDestructionStructs.push_back(GenerateDestructionStruct());
    mDestructionStructs.back().mType = _type;
    mDestructionStructs.back().mOperation = _operation;
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::GetArrayOf(WNScriptType& _type, WNScriptType& _outType) {
    if(!_type) {
        return(eWNDoesNotExist);
    }
    if(_type->mArrayParent) {
        _outType = _type->mArrayParent;
        return(eWNOK);
    }

    llvm::PointerType* ty = _type->mLLVMType->getPointerTo(0);
#ifdef _WN_64_BIT
    llvm::Type* arrayRefCount = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* arrayRefCount = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif
    llvm::Type* types[3] = { llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext()), ty, arrayRefCount };
    llvm::StructType* sIndirectionType = llvm::StructType::create(llvm::getGlobalContext(), llvm::makeArrayRef(types));

    ty = sIndirectionType->getPointerTo(0);
    WNScripting::WNScriptTypeInternal* scriptType = WN_NEW WNScriptTypeInternal(_type, ty, 0, mNextTag);
    IncrementTag();
    _outType = scriptType;
    WNScriptType nullType;
    WNScriptType  boolType;
    WNScriptType intType;
    if((eWNOK != GetTypeByName("-Null", nullType)) || (eWNOK != GetTypeByName("Bool", boolType)) || (eWNOK != GetTypeByName("Int", intType)))
    {
        return(eWNPlatformError);
    }

    RegisterArrayAccessOperator(scriptType, intType, WN_NEW GenerateDefaultArrayAccessOperation(scriptType));
    RegisterDestructionOperator(scriptType, WN_NEW GenerateArrayDestruction());
    RegisterAllocationOperator(scriptType, WN_NEW GenerateDefaultAllocation());
    RegisterAssignmentOperator(scriptType, AT_EQ, WN_NEW GenerateStructAssignment());
    RegisterAssignmentOperator(scriptType, AT_CHOWN, WN_NEW GenerateStructTransfer());
    RegisterCastingOperator(nullType, scriptType, WN_NEW GenerateReinterpretCastOperation(scriptType));
    RegisterArithmeticOperator(AR_EQ, scriptType, scriptType, WN_NEW GenerateStructCompare(boolType, WN_TRUE));
    RegisterArithmeticOperator(AR_NEQ, scriptType, scriptType, WN_NEW GenerateStructCompare(boolType, WN_FALSE));
    return(eWNOK);
}

eWNTypeError WNTypeManagerImpl::GetTypeByName(const WN_CHAR* name, WNScriptType& _outType) const {
    for(std::list<WNScriptType>::const_iterator i = mScriptTypes.begin(); i != mScriptTypes.end(); ++i) {
        if(WNStrings::WNStrNCmp((*i)->mName, name, 256) == 0){
            _outType = *i;
            return(eWNOK);
        }
    }
    return(eWNDoesNotExist);
}

const GenerateArithmeticOperation* WNTypeManagerImpl::GetArithmeticOperation(WNArithmeticType _arith, WNScriptType _lhs, WNScriptType _rhs) const {
    for(std::list<GenerateArithmeticStruct>::const_iterator i = mArithmeticStructs.begin(); i != mArithmeticStructs.end(); ++i) {
        if(i->mType1 == _lhs && i->mType2 == _rhs && i->mArithmeticType == _arith) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateCastingOperation* WNTypeManagerImpl::GetCastingOperation(WNScriptType _lhs, WNScriptType _rhs) const {
    for(std::list<GenerateCastingStruct>::const_iterator i = mCastingStructs.begin(); i != mCastingStructs.end(); ++i) {
        if(i->mType1 == _lhs && i->mType2 == _rhs) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GeneratePreUnaryOperation* WNTypeManagerImpl::GetPreUnaryOperation(WNUnaryType _type, WNScriptType _lhs) const {
    for(std::list<GeneratePreUnaryStruct>::const_iterator i = mPreUnaryStructs.begin(); i != mPreUnaryStructs.end(); ++i) {
        if(i->mType == _lhs && i->mUnaryType == _type) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GeneratePostUnaryOperation* WNTypeManagerImpl::GetPostUnaryOperation(WNPostUNType _type, WNScriptType _operand) const {
    for(std::list<GeneratePostUnaryStruct>::const_iterator i = mPostUnaryStructs.begin(); i != mPostUnaryStructs.end(); ++i) {
        if(i->mPostUnaryType == _type && i->mType == _operand) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateArrayAccessOperation* WNTypeManagerImpl::GetArrayAccessOperation(WNScriptType _operand1, WNScriptType _operand2) const {
    for(std::list<GenerateArrayAccessStruct>::const_iterator i = mArrayAccessStructs.begin(); i != mArrayAccessStructs.end(); ++i) {
        if(i->mType1 == _operand1 && i->mType2 == _operand2) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateIDAccessOperation* WNTypeManagerImpl::GetIDAccessOperation(WNScriptType _type) const {
    for(std::list<GenerateIDAccessStruct>::const_iterator i = mIDAccessStructs.begin(); i != mIDAccessStructs.end(); ++i) {
        if(i->mType1 == _type) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateConstantOperation* WNTypeManagerImpl::GetConstantOperation(WNScriptType _type) const {
    for(std::list<GenerateConstantStruct>::const_iterator i = mConstantStructs.begin(); i != mConstantStructs.end(); ++i) {
        if(i->mType == _type) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateAllocation* WNTypeManagerImpl::GetAllocationOperation(WNScriptType _type) const {
    for(std::list<GenerateAllocationStruct>::const_iterator i = mAllocationStructs.begin(); i != mAllocationStructs.end(); ++i) {
        if(i->mType == _type) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateAssignment* WNTypeManagerImpl::GetAssignmentOperation(WNScriptType _type, WNAssignType _assignType) const {
    for(std::list<GenerateAssignmentStruct>::const_iterator i = mAssignmentStructs.begin(); i != mAssignmentStructs.end(); ++i) {
        if(i->mType == _type && i->mAssignType == _assignType) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

const GenerateDestruction* WNTypeManagerImpl::GetDestructionOperation(WNScriptType _type) const {
    for(std::list<GenerateDestructionStruct>::const_iterator i = mDestructionStructs.begin(); i != mDestructionStructs.end(); ++i) {
        if(i->mType == _type) {
            return(i->mOperation);
        }
    }
    return(WN_NULL);
}

GenerateDefaultStructAccessor::GenerateDefaultStructAccessor(WNScriptType _structType):
    mStructType(_structType){
}

eWNTypeError GenerateDefaultStructAccessor::Execute(llvm::IRBuilderBase* _builder, llvm::Value*, llvm::Value* _structLoc, WN_CHAR* _id, WNScriptType& _outType, llvm::Value*& _outValue, llvm::Value*& _outLocation) const
{
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_builder);
    size_t valueNum = 0;
    for(std::vector<WNContainedStructType>::iterator i = mStructType->mStructTypes.begin(); i != mStructType->mStructTypes.end(); ++i, valueNum++) {
        if(WNStrings::WNStrNCmp(i->mName, _id, 256) == 0){
            _outType = i->mType;
            break;
        }
    }
    if(valueNum >= mStructType->mStructTypes.size()) {
        return(eWNDoesNotExist);
    }
    
    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* tempValue = builder->CreateLoad(builder->CreateGEP(_structLoc, GepArray));
    
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    tempValue = builder->CreateLoad(builder->CreateGEP(tempValue, GepArray));

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), valueNum));
    
    _outLocation = builder->CreateGEP(tempValue, GepArray);
    
    _outValue = builder->CreateLoad(_outLocation, false, "");
    return(eWNOK);
}

eWNTypeError GenerateDefaultAllocation::Execute(WNCodeModule& _module, WNScriptType _type, const WN_CHAR* _name, WN_BOOL, llvm::Value*& _outLocation) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    if(!_type->mLLVMType) {
        return(eWNBadType);
    }
    _outLocation = builder->CreateAlloca(_type->mLLVMType, 0, _name);
    return(eWNOK);
}

eWNTypeError GenerateDefaultAssignment::Execute(WNCodeModule& _module, bool, WNScriptType, llvm::Value* _value, llvm::Value* _assignLocation, const WNFunctionDefinition*, WNLogging::WNLog&) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    builder->CreateStore(_value, _assignLocation);
    return(eWNOK);
}

eWNTypeError GenerateStructAssignment::Execute(WNCodeModule& _module, bool _declaration, WNScriptType _type, llvm::Value* _value, llvm::Value* _assignLocation, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    std::vector<llvm::Value*> GepArray;

    if(!_declaration) {
        //First check what was currently in there, if we were the owner then destroy it
        eWNTypeError err = eWNOK;
        static GenerateStructDestruction structDest;
        if(eWNOK != (err = structDest.Execute(_module, _type, _assignLocation, _def, _compilationLog))) {
            return(err);
        }

        //Now reduce the ref-count on the smart-ptr if that goes to 0, then delete it
    }

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* ownerLocation = builder->CreateGEP(_value, GepArray);
    llvm::Value* tempValue = builder->CreateLoad(ownerLocation);

    llvm::BasicBlock* assignBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "AssignStruct", _def->mFunction);
    llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "Continue", _def->mFunction);
    
#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif

    llvm::Value* cmp = builder->CreateICmpEQ(builder->CreatePtrToInt(tempValue, ptrType), llvm::ConstantInt::get(ptrType, 0));
    builder->CreateCondBr(cmp, assignBlock, endBlock);
    builder->SetInsertPoint(assignBlock);
    llvm::Value* ptrVal = builder->CreatePointerCast(_assignLocation, llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext()));
    builder->CreateStore(ptrVal, ownerLocation);
    builder->CreateBr(endBlock);
    builder->SetInsertPoint(endBlock);

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
    llvm::Value* countLocation = builder->CreateGEP(_value, GepArray);
    builder->CreateAtomicRMW(llvm::AtomicRMWInst::Add, countLocation, llvm::ConstantInt::get(ptrType, 1), llvm::Monotonic);
    builder->CreateStore(_value, _assignLocation);
    return(eWNOK);
}

eWNTypeError GenerateStructTransfer::Execute(WNCodeModule& _module, bool _declaration, WNScriptType _type, llvm::Value* _value, llvm::Value* _assignLocation, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    std::vector<llvm::Value*> GepArray;

    llvm::Value* assignLocationTemp = NULL;
    if(!_declaration) {
        assignLocationTemp = builder->CreateAlloca(_type->mLLVMType, 0);
        builder->CreateStore(builder->CreateLoad(_assignLocation), assignLocationTemp);
    }

            
#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* ownerLocation = builder->CreateGEP(_value, GepArray);
    llvm::Value* cmp = NULL;

    llvm::Value* ptrVal = builder->CreatePointerCast(_assignLocation, llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext()));
    builder->CreateStore(ptrVal, ownerLocation);

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
    llvm::Value* countLocation = builder->CreateGEP(_value, GepArray);
    builder->CreateAtomicRMW(llvm::AtomicRMWInst::Add, countLocation, llvm::ConstantInt::get(ptrType, 1), llvm::Monotonic);
    builder->CreateStore(_value, _assignLocation);
    if(!_declaration) {
        
        llvm::BasicBlock* transferToTemp = llvm::BasicBlock::Create(llvm::getGlobalContext(), "TransferToTemp", _def->mFunction);
        llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "EndTransferToTemp", _def->mFunction);
        
        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        llvm::Value* tempValueLocation =  builder->CreateLoad(builder->CreateGEP(assignLocationTemp, GepArray));
        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        llvm::Value* tempOwnerLocation = builder->CreateGEP(tempValueLocation, GepArray);
        cmp = builder->CreateICmpEQ(builder->CreatePtrToInt(builder->CreateLoad(tempOwnerLocation), ptrType), builder->CreatePtrToInt(_assignLocation, ptrType));

        builder->CreateCondBr(cmp, transferToTemp, endBlock);
        builder->SetInsertPoint(transferToTemp);
    
        
        builder->CreateStore(builder->CreatePointerCast(assignLocationTemp, llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext())), tempOwnerLocation);
        builder->CreateBr(endBlock);
        builder->SetInsertPoint(endBlock);
        //First check what was currently in there, if we were the owner then destroy it
        eWNTypeError err = eWNOK;
        static GenerateStructDestruction structDest;
        if(eWNOK != (err = structDest.Execute(_module, _type, assignLocationTemp, _def, _compilationLog))) {
            return(err);
        }
    }
    return(eWNOK);
}


eWNTypeError GenerateStructDestruction::Execute(WNCodeModule& _module, WNScriptType _node, llvm::Value* _structLoc, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    
    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* spLoc = builder->CreateLoad(builder->CreateGEP(_structLoc, GepArray));
    
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* tempValue = builder->CreateLoad(builder->CreateGEP(spLoc, GepArray));

    llvm::BasicBlock* destroyBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "DestroyStruct", _def->mFunction);
    llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "Continue", _def->mFunction);

#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif

    llvm::Value* cmp = builder->CreateICmpEQ(builder->CreatePtrToInt(tempValue, ptrType), builder->CreatePtrToInt(_structLoc, ptrType));
    builder->CreateCondBr(cmp, destroyBlock, endBlock);
    builder->SetInsertPoint(destroyBlock);

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* objectLocationLocation = builder->CreateGEP(spLoc, GepArray);
    tempValue = builder->CreateLoad(objectLocationLocation);

    WNScriptType voidStarType;

    if(eWNOK != _module.GetTypeManager().GetTypeByName("-Ptr", voidStarType)) {
        return(eWNPlatformError);
    }
    std::vector<WNScriptType> scriptTypes;
    scriptTypes.push_back(voidStarType);
    WNFunctionDefinition* freeDef = 0;
    if(0 == (freeDef = _module.GetFunctionDefinition("Free", scriptTypes))) {
        return(eWNPlatformError);
    }

    WN_SIZE_T nameLen = WNStrings::WNStrLen(_node->mName);
    WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR)* (nameLen + 1 + 5)));
    WNMemory::WNMemCpy(functionName, _node->mName, nameLen);
    WNMemory::WNMemCpy(functionName + nameLen, "Destr", 5);
    functionName[nameLen + 5] = '\0';
    
    std::vector<FunctionParam> fParams;
    fParams.push_back(FunctionParam());
    fParams.back().mType = _node;
    fParams.back().mValue = spLoc;

    WNScriptType returnType;
    llvm::Value* returnValue;
    
    llvm::BasicBlock* notNull = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    cmp = builder->CreateICmpNE(builder->CreatePtrToInt(tempValue, ptrType, ""), llvm::ConstantInt::get(ptrType, 0));
    builder->CreateCondBr(cmp, notNull, endBlock);
    builder->SetInsertPoint(notNull);

    if(eWNOK != (GenerateFunctionCall(_module, _def, functionName, fParams, false, returnType, returnValue, _compilationLog))) {
        return(eWNPlatformError);
    }
    llvm::Value* freeParams[1] = { builder->CreatePointerCast(tempValue, voidStarType->mLLVMType) };
    builder->CreateCall(freeDef->mFunction, llvm::makeArrayRef(freeParams));
    builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(ptrType, 0), _node->mLLVMStructType->getPointerTo(0)), objectLocationLocation);

    builder->CreateBr(endBlock);
    builder->SetInsertPoint(endBlock);
    
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
    tempValue = builder->CreateGEP(spLoc, GepArray);
    
    llvm::Value* val = builder->CreateAtomicRMW(llvm::AtomicRMWInst::Sub, tempValue, llvm::ConstantInt::get(ptrType, 1), llvm::Monotonic);
    llvm::BasicBlock* deleteScopeBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    
    cmp = builder->CreateICmpSLE(val, llvm::ConstantInt::get(ptrType, 1)); 

    builder->CreateCondBr(cmp, deleteScopeBlock, endBlock);
    builder->SetInsertPoint(deleteScopeBlock);

    llvm::Value* freeParams2[1] = { builder->CreatePointerCast(spLoc, voidStarType->mLLVMType) };
    builder->CreateCall(freeDef->mFunction, llvm::makeArrayRef(freeParams2));
    builder->CreateBr(endBlock);

    builder->SetInsertPoint(endBlock);
    return(eWNOK);
}

eWNTypeError GenerateReinterpretCastOperation::Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value*& _outReturnValue) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_builder);
    _outReturnValue = builder->CreatePointerCast(_expr1, mScriptType->mLLVMType);
    return(eWNOK);
}


eWNTypeError GenerateStructCompare::Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value* _expr2, WNScriptType& _destType, llvm::Value*& _outReturnVal) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);
    
            
#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif

    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* lhsValue = builder->CreateLoad(builder->CreateGEP(_expr1, GepArray));
    llvm::Value* rhsValue = builder->CreateLoad(builder->CreateGEP(_expr2, GepArray));
    lhsValue = builder->CreatePtrToInt(lhsValue, ptrType);
    rhsValue = builder->CreatePtrToInt(rhsValue, ptrType);
    
    llvm::Value* cmp = NULL;
    if(mEq) {
        cmp = builder->CreateICmpEQ(lhsValue, rhsValue);
    } else {
        cmp = builder->CreateICmpNE(lhsValue, rhsValue);
    }
    _outReturnVal = builder->CreateIntCast(cmp, mBoolType->mLLVMType, false);
    
    _destType = mBoolType;
    return(eWNOK);
}

eWNTypeError GenerateDefaultArrayAccessOperation::Execute(WNCodeModule& _module, llvm::Value* _arrayLoc, llvm::Value* _elemNumber, llvm::Value*& _outValue, llvm::Value*& _outLocation, const WNFunctionDefinition* _def) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());

#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
    const size_t ptrSize = 8;
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
    const size_t ptrSize = 4;
#endif

    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* tempValue = _arrayLoc;//oad(builder->CreateGEP(_arrayLoc, GepArray));

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* arrayLocation = builder->CreateLoad(builder->CreateGEP(tempValue, GepArray));
    llvm::Value* intPtr = builder->CreatePointerCast(arrayLocation, ptrType->getPointerTo(0));
    arrayLocation = builder->CreatePtrToInt(arrayLocation, ptrType);
    arrayLocation = builder->CreateAdd(arrayLocation , llvm::ConstantInt::get(ptrType, ptrSize));
    arrayLocation = builder->CreateIntToPtr(arrayLocation, mScriptType->mArrayType->mLLVMType->getPointerTo(0));
   
    llvm::Value* arraySize = builder->CreateLoad(intPtr);
    llvm::Value* elemNumber = builder->CreateIntCast(_elemNumber, ptrType, true);
    
    llvm::Value* cmp = builder->CreateICmpSGE(elemNumber, llvm::ConstantInt::get(ptrType, 0)); 
    cmp = builder->CreateAnd(cmp, builder->CreateICmpSLT(elemNumber, arraySize));
    llvm::BasicBlock* oobBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "oobBlock", _def->mFunction);
    llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    builder->CreateCondBr(cmp, endBlock, oobBlock);
    builder->SetInsertPoint(oobBlock);
    GenerateAssertion(_module);
    builder->CreateBr(endBlock);
    builder->SetInsertPoint(endBlock);
    GepArray.clear();
    GepArray.push_back(elemNumber);
    
    _outLocation = builder->CreateGEP(arrayLocation, GepArray);
    
    _outValue = builder->CreateLoad(_outLocation, false, "");
    return(eWNOK);   
}

eWNTypeError GenerateArrayDestruction::Execute(WNCodeModule& _module, WNScriptType _node, llvm::Value* _structLoc, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) const {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    
    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* spLoc = builder->CreateLoad(builder->CreateGEP(_structLoc, GepArray));
    
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    llvm::Value* tempValue = builder->CreateLoad(builder->CreateGEP(spLoc, GepArray));

    llvm::BasicBlock* destroyBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "DestroyArray", _def->mFunction);
    llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "Continue", _def->mFunction);

#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
    WN_SIZE_T ptrSize = 8;
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
    WN_SIZE_T ptrSize = 4;
#endif

    llvm::Value* cmp = builder->CreateICmpEQ(builder->CreatePtrToInt(tempValue, ptrType), builder->CreatePtrToInt(_structLoc, ptrType));
    builder->CreateCondBr(cmp, destroyBlock, endBlock);
    builder->SetInsertPoint(destroyBlock);

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* objectLocationLocation = builder->CreateGEP(spLoc, GepArray);
    tempValue = builder->CreateLoad(objectLocationLocation);

    WNScriptType voidStarType;
    eWNTypeError err = eWNOK;
    if(eWNOK != _module.GetTypeManager().GetTypeByName("-Ptr", voidStarType)) {
        return(eWNPlatformError);
    }
    std::vector<WNScriptType> scriptTypes;
    scriptTypes.push_back(voidStarType);
    WNFunctionDefinition* freeDef = 0;
    if(0 == (freeDef = _module.GetFunctionDefinition("Free", scriptTypes))) {
        return(eWNPlatformError);
    }

    llvm::BasicBlock* notNull = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    cmp = builder->CreateICmpNE(builder->CreatePtrToInt(tempValue, ptrType, ""), llvm::ConstantInt::get(ptrType, 0));
    builder->CreateCondBr(cmp, notNull, endBlock);
    builder->SetInsertPoint(notNull);

    const GenerateDestruction* destOp = _module.GetTypeManager().GetDestructionOperation(_node->mArrayType);
    if(destOp)
    {
        llvm::BasicBlock* loopHeader = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
        llvm::BasicBlock* loopBody   = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
        llvm::BasicBlock* loopEnd    = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
        llvm::Value* loopLoc = builder->CreateAlloca(ptrType);
        llvm::Value* arraySize = builder->CreateLoad(builder->CreatePointerCast(tempValue, ptrType->getPointerTo(0)));
        llvm::Value* arrayBase = builder->CreatePtrToInt(tempValue, ptrType);
        arrayBase = builder->CreateAdd(arrayBase, llvm::ConstantInt::get(ptrType, ptrSize));
        arrayBase = builder->CreateIntToPtr(arrayBase, _node->mArrayType->mLLVMType->getPointerTo(0));
        builder->CreateStore(llvm::ConstantInt::get(ptrType, 0), loopLoc);
        builder->CreateBr(loopHeader);
        builder->SetInsertPoint(loopHeader);
        llvm::Value* validSize = builder->CreateICmpULT(builder->CreateLoad(loopLoc, false), arraySize); 
        builder->CreateCondBr(validSize, loopBody, loopEnd);
        builder->SetInsertPoint(loopBody);

        llvm::Value* currentElement = builder->CreateGEP(arrayBase, builder->CreateLoad(loopLoc) );
        
        if(eWNOK != (err = destOp->Execute(_module, _node->mArrayType, currentElement, _def, _compilationLog))) {
            return(err);
        }

        builder->CreateStore(builder->CreateAdd(builder->CreateLoad(loopLoc), llvm::ConstantInt::get(ptrType, 1)), loopLoc);
        builder->CreateBr(loopHeader);
        builder->SetInsertPoint(loopEnd);
    }

    llvm::Value* freeParams[1] = { builder->CreatePointerCast(tempValue, voidStarType->mLLVMType) };
    builder->CreateCall(freeDef->mFunction, llvm::makeArrayRef(freeParams));
    builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(ptrType, 0), _node->mArrayType->mLLVMType->getPointerTo(0)), objectLocationLocation);

    builder->CreateBr(endBlock);
    builder->SetInsertPoint(endBlock);
    
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
    tempValue = builder->CreateGEP(spLoc, GepArray);
    
    llvm::Value* val = builder->CreateAtomicRMW(llvm::AtomicRMWInst::Sub, tempValue, llvm::ConstantInt::get(ptrType, 1), llvm::Monotonic);
    llvm::BasicBlock* deleteScopeBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    
    cmp = builder->CreateICmpSLE(val, llvm::ConstantInt::get(ptrType, 1)); 

    builder->CreateCondBr(cmp, deleteScopeBlock, endBlock);
    builder->SetInsertPoint(deleteScopeBlock);

    llvm::Value* freeParams2[1] = { builder->CreatePointerCast(spLoc, voidStarType->mLLVMType) };
    builder->CreateCall(freeDef->mFunction, llvm::makeArrayRef(freeParams2));
    builder->CreateBr(endBlock);

    builder->SetInsertPoint(endBlock);
    return(eWNOK);
}
